(ns pe.p19
  (:use pe.digits))

;You are given the following information, but you may prefer to do some research for yourself.

;1 Jan 1900 was a Monday.
;Thirty days has September,
;April, June and November.
;All the rest have thirty-one,
;Saving February alone,
;Which has twenty-eight, rain or shine.
;And on leap years, twenty-nine.
;A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400.
;How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?

(def month-lengths {:January 31,
                    :February 28,
                    :March 31,
                    :April 30,
                    :May 31,
                    :June 30,
                    :July 31,
                    :August 31,
                    :September 30,
                    :October 31,
                    :November 30,
                    :December 31})

(def month-order {:January 1,
                  :February 2,
                  :March 3,
                  :April 4,
                  :May 5,
                  :June 6,
                  :July 7,
                  :August 8,
                  :September 9,
                  :October 10,
                  :November 11,
                  :December 12})

(defstruct date :year :month :day :dow)

(defn lazy-months[] (cycle [:January, :February, :March, :April, :May, :June, :July, :August, :September, :October,
                          :November, :December]))

(defn lazy-dows[] (cycle [:Monday, :Tuesday, :Wednesday, :Thursday, :Friday, :Saturday, :Sunday]))

(defn take-next [coll x]
  (fnext (drop-while #(not (= x %)) coll)))

(defn next-month [month] (take-next (lazy-months) month))
(defn next-dow [dow] (take-next (lazy-dows) dow))

(defn leap? [year]
  (or (and (divides? year 4) (not (divides? year 100)))
      (divides? year 400)))

(defn days-in-month [year month]
  (let [days (month-lengths month)]
    (if (and (= :February month) (leap? year)) (inc days) days)))

(defn next-date [{year :year, month :month, day :day, dow :dow}]
  (let [days (days-in-month year month)
        shift-month (> (inc day) days)
        shift-year (and shift-month (identical? month :December))
        nday (if shift-month 1 (inc day))
        nmonth (if shift-month (next-month month) month)
        nyear (if shift-year (inc year) year)
        ndow (next-dow dow)]
    (struct date nyear nmonth nday ndow)))

(defn lazy-dates []
  (iterate next-date (struct date 1900 :January 1 :Monday)))

(defn compare-dates [{year1 :year, month1 :month, day1 :day, dow1 :dow}
                     {year2 :year, month2 :month, day2 :day, dow2 :dow}]
  (let [yd (- year1 year2) md (- (month-order month1) (month-order month2)) dd (- day1 day2)]
  (cond
    (or (neg? yd) (pos? yd)) yd
    (or (neg? md) (pos? md)) md
    :else dd)))

(defn date-after? [base date]
  (neg? (compare-dates base date)))

(defn date-before? [base date]
  (pos? (compare-dates base date)))

(def century-start (struct date 1901 :January 1 :Sunday)) ;:Sunday is fictional, doesn't matter
(def next-century-start (struct date 2001 :January 1 :Sunday))

(println
  (count (filter (fn [{day :day dow :dow}] (and (= dow :Sunday) (= day 1)))
    (drop-while #(date-before? century-start %) (take-while #(date-before? next-century-start %) (lazy-dates))))))